﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Text.RegularExpressions;
using log4net;
using RollBackLib.src.utils;


namespace RollBackLib.src.core
{
    /// <summary>
    /// Contains all the information required to run a threaded backup job.
    /// </summary>
    public class BackupInfo
    {
        /// <summary>
        /// The current run status of the backup job
        /// </summary>
        public enum RunStatus
        {
            NotRunning,
            Running,
            Paused
        };

        public enum LastRunResult
        {
            FAILED,
            ABORTED,
            FINISHED
        }

        /// <summary>
        /// The kind of backup that is currently running which is one of backup,resotre etc.
        /// </summary>
        public enum BackupType
        {
            BACKUP,           /// Running a backup
            SCHEDULED_BACKUP, /// Scheduled backup
            RESTORE,          /// Running a restore operation
            DELETE_BACKUP,    /// Deleting the backup directory because the backup job is being removed.
        }

        private static readonly ILog log = LogManager.GetLogger(typeof(BackupInfo));

        /// <summary>
        /// Start a backup operation without a job
        /// </summary>
        /// <param name="backupType"></param>
        /// <param name="handler"></param>
        public BackupInfo(BackupType backupType,
            StatusUpdateHandler handler)
        {
            internalInit(null, null, backupType, null, handler);
        }

        /// <summary>
        /// Start a backup job with the following params
        /// 1. Get the list of objects to be backed up from the backup job
        /// 2. Get the destination directory from the backup job.
        /// </summary>
        /// <param name="backupType"></param>
        /// <param name="job"></param>
        /// <param name="handler"></param>
        public BackupInfo(BackupType backupType,
            BackupJob job,
            StatusUpdateHandler handler
            )
        {
            if (job == null)
            {
                throw new BackupException("Job cannot be null");
            }
            internalInit(job.m_BackupPathList, job.m_DestDir, backupType, job, handler);
        }

        public BackupInfo(List<BackupItem> items,
            string destDir,
            BackupType backupType,
            BackupJob job,
            StatusUpdateHandler handler
            )
        {
            internalInit(items, destDir, backupType, job, handler);
        }

        private void internalInit(List<BackupItem> items, string destDir, BackupType backupType, BackupJob job, StatusUpdateHandler handler)
        {
            this.m_Items = items;
            this.m_DestDir = destDir;
            this.m_Job = job;
            m_updateHandler = handler;
            m_Statistics = new BackupStatistics();
            if (job != null)
            {
                m_Statistics.m_Id = job.m_Id;
            }
            m_LastExceptionDisplayed = true;

            m_includeFileFilter = new List<Regex>();
            m_excludeFileFilter = new List<Regex>();
            m_includeFolderFilter = new List<Regex>();
            m_excludeFolderFilter = new List<Regex>();

            m_includeFileFilterStr = "";
            m_excludeFileFilterStr = "";
            m_includeFolderFilterStr = "";
            m_excludeFolderFilterStr = "";
            m_Statistics.m_BackupType = backupType;
        }

        /// <summary>
        /// Call this function to process the include/exclude filter regular expression.
        /// </summary>
        /// <returns>On Failure: Returns the list of exceptions generated when the regular expression filters were compiled.
        ///          On Success: Returns an empty string "".
        /// </returns>
        public string processIncludeExcludeFilters( )
        {
            string exceptionStr = "";
            m_includeFileFilterStr = compileRegex(m_Job.m_includeFilePattern, m_includeFileFilter,"File Include",ref exceptionStr);
            m_excludeFileFilterStr = compileRegex(m_Job.m_excludeFilePattern, m_excludeFileFilter,"File Exclude",ref exceptionStr);
            m_includeFolderFilterStr = compileRegex(m_Job.m_includeFolderPattern, m_includeFolderFilter,"Folder Include",ref exceptionStr);
            m_excludeFolderFilterStr = compileRegex(m_Job.m_excludeFolderPattern, m_excludeFolderFilter,"Folder Exclude",ref exceptionStr);
            return exceptionStr;
        }

        /// <summary>
        /// Compile the list of Regular expression strings.
        /// </summary>
        /// <param name="pattern"></param>
        /// <param name="l"></param>
        /// <param name="comment"></param>
        /// <param name="exceptionStr"></param>
        /// <returns></returns>
        string compileRegex(RegexFilter filter,List<Regex> compiledRegexp,string comment, ref string exceptionStr)
        {
            string filterStr = "";
            string _exceptionStr = "";
            foreach (string pat in filter.m_Expressions)
            {
                string str = pat.Trim();
                try
                {
                    string s = str;
                    if (s.Length > 0)
                    {
                        if (s.StartsWith("*"))
                        {
                            s = "." + s;
                        }
                        Regex exp = new Regex(s, RegexOptions.Compiled | RegexOptions.IgnoreCase);
                        compiledRegexp.Add(exp);
                        filterStr += str.Trim() + "; ";
                    }
                }
                catch (Exception e)
                {
                    log.Warn("Error compiling regular expression <" + str + ">", e);
                    // catch those string which causes a regular expression excpetion
                    _exceptionStr += "Pattern=<" + str + ">       Exception=<" + e.Message + ">\r\n";
                }
                if (_exceptionStr.Length > 0)
                {
                    exceptionStr = exceptionStr + "\r\n" + comment + "\r\n" + _exceptionStr;
                }
            }
            return filterStr;
        }


        public void callUpdateHandler( )
        {
            if (m_updateHandler != null)
            {
                m_updateHandler(this);
            }
        }

        /// <summary>
        /// Observer which gets called after every copy operation
        /// </summary>
        public delegate void StatusUpdateHandler(BackupInfo backupInfo);

        /// <summary>
        /// list of items like folder name/file name to be backed up or restored
        /// </summary>
        public List<BackupItem> m_Items
        {
            get;
            set;
        }

        /// <summary>
        /// Destination directory where the data will be backed up.
        /// </summary>
        public string m_DestDir
        {
            get;
            set;
        }

        /// <summary>
        /// The actual backup job which will be updated with 'items'
        /// </summary>
        public BackupJob m_Job
        {
            get;
            set;
        }

        /// <summary>
        ///  the status update handler which will be notified 
        /// </summary>
        private StatusUpdateHandler m_updateHandler;

        private Exception m_lastException;
        /// <summary>
        /// The last exception encountered when running this backup job thread
        /// </summary>
        public Exception m_LastException
        {
            get { 
                return m_lastException;
            }
            set {
                m_LastExceptionDisplayed = false;
                m_lastException = value;
            }
        }

        /// <summary>
        /// Flag to indicate whether the exception was already displayed
        /// </summary>
        public bool m_LastExceptionDisplayed
        {
            get;
            set;
        }

        /// <summary>
        /// Statistics about the currently running backup like number of files, directories,
        /// error codes etc.
        /// </summary>
        public BackupStatistics m_Statistics {
            get;
            set;
        }

        public List<Regex> m_includeFileFilter;
        public List<Regex> m_excludeFileFilter;
        public List<Regex> m_includeFolderFilter;
        public List<Regex> m_excludeFolderFilter;

        public string m_includeFileFilterStr;
        public string m_excludeFileFilterStr;
        public string m_includeFolderFilterStr;
        public string m_excludeFolderFilterStr;

        /// <summary>
        /// What was the result of the last run
        /// </summary>
        public LastRunResult m_lastRunSuccess;

        public bool isBackup()
        {
            bool flag =
                m_Statistics.m_BackupType == BackupType.BACKUP ||
                m_Statistics.m_BackupType == BackupType.SCHEDULED_BACKUP;
            return flag;
        }

        public bool isInteractiveBackup()
        {
            bool flag = m_Statistics.m_BackupType == BackupType.BACKUP;
            return flag;
        }

        public bool isDeleteOperation()
        {
            bool flag =
                m_Statistics.m_BackupType == BackupType.DELETE_BACKUP;
            return flag;
        }


        public string createJobEndStatus( )
        {
            BackupInfo bInfo = this;
            DateTime end = bInfo.m_Statistics.m_stopped = DateTime.Now;
            TimeSpan elapsed = end.Subtract(bInfo.m_Statistics.m_started);
            string timespanString = string.Format("{0} days   {1} h: {2} m: {3} s", elapsed.Days, elapsed.Hours, elapsed.Minutes, elapsed.Seconds);

            string startStr = bInfo.m_Statistics.m_BackupType + " started: " + bInfo.m_Statistics.m_started.ToLocalTime().ToString();
            string finishStr = "";
            if (bInfo.m_Statistics.m_backupWasStopped)
            {
                finishStr = "Aborted: " + end.ToLocalTime().ToString();
                bInfo.m_Job.registerRunAbort();
            }
            else
            {
                finishStr = "Finished: " + end.ToLocalTime().ToString();
                bInfo.m_Job.registerRunStop();
            }
            bInfo.m_lastRunSuccess = bInfo.m_Job.m_lastRunSuccess;

            BackupStatistics stats = bInfo.m_Statistics;

            string line =
                startStr + "   " + finishStr + "    " + "\r\n"
                + "Took=" + timespanString + ", Machine="+AppUtils.getHostname() + "\r\n"
                + ""
                //+ "Files: Copied=" + stats.m_fileStats.m_copied + "       Skipped=" + stats.m_fileStats.m_skipped + "        Deleted=" + stats.m_fileStats.m_deleted + "\r\n"
                //+ "Directories: Scanned=" + stats.m_dirStats.m_copied + "       Skipped=" + stats.m_dirStats.m_skipped + "       Created=" + stats.m_dirStats.m_created + "        Deleted=" + stats.m_dirStats.m_deleted
                ;
            return line;
        }

        /// <summary>
        /// Create the Statistics about the number of files/directories scanned/copied etc.
        /// </summary>
        /// <returns></returns>
        public String createStatsHtmlTable( )
        {
            BackupJob job = this.m_Job;
            BackupStatistics stats = this.m_Statistics;
            String headerColor = "LightBlue";
            String statsHtmlTable =
                "<table border=\"1\">" +
                "<caption>Statistics for Job " + job.m_Id.m_Name + " </caption>" +
                "<tr>" +
                "<th bgcolor= " + headerColor + ">Type</th>" +
                "<th bgcolor= " + headerColor + ">Copied</th>" +
                "<th bgcolor= " + headerColor + ">Skipped</th>" +
                "<th bgcolor= " + headerColor + ">Deleted</th>" +
                "</tr>" +
                "<tr>" +
                "<td>File(s)</td>" +
                "<td>" + stats.m_fileStats.m_copied + "</td>" +
                "<td>" + stats.m_fileStats.m_skipped + "</td>" +
                "<td>" + stats.m_fileStats.m_deleted + "</td>" +
                "</tr>" +
                "<tr>" +
                "<td>Directory(s)</td>" +
                "<td>" + stats.m_dirStats.m_copied + "</td>" +
                "<td>" + stats.m_dirStats.m_skipped + "</td>" +
                "<td>" + stats.m_dirStats.m_deleted + "</td>" +
                "</tr>" +
                "</table>";
            return statsHtmlTable;
        }

        /// <summary>
        /// Create the HTML table giving the breakup of frequency of various file types like images/video etc.
        /// </summary>
        /// <returns></returns>
        public string createFileTypesHtmlTable()
        {
            BackupJob job = this.m_Job;
            String headerColor = "LightBlue";
            String fileTypesHtmlTable =
                "<table border=\"1\">" +
                "<caption>File types for Job " + job.m_Id.m_Name + " </caption>" +
                "<tr>" +
                "<th bgcolor= " + headerColor + ">Type</th>" +
                "<th bgcolor= " + headerColor + ">Count</th>" +
                "<th bgcolor= " + headerColor + ">Total Size</th>" +
                "";
            /*
                "<tr><td>Images</td> <td>" + types.m_images.m_count + "</td> <td>" + AppUtils.toHumanReadableSize(types.m_images.m_totalSize) + "</td> </tr>" +
                "<tr><td>Videos</td> <td>" + types.m_videos.m_count + "</td> <td>" + AppUtils.toHumanReadableSize(types.m_videos.m_totalSize) + "</td> </tr>" +
                "<tr><td>Documents</td> <td>" + types.m_documents.m_count + "</td> <td>" + AppUtils.toHumanReadableSize(types.m_documents.m_totalSize) + "</td> </tr>" +
                "<tr><td>Others</td> <td>" + types.m_others.m_count + "</td> <td>" + AppUtils.toHumanReadableSize(types.m_others.m_totalSize) + "</td> </tr>" +
                "</table>";
            */
            foreach (BackupStatistics.FileType ftype in this.m_Statistics.m_typeCount.m_typeList)
            {
                String s = "<tr><td>" + ftype.m_name +
                    "</td> <td>" + ftype.m_count + "</td> <td> " +
                    AppUtils.toHumanReadableSize(ftype.m_totalSize) +
                    "</td></tr>";
                fileTypesHtmlTable += s;
            }
            fileTypesHtmlTable += "</table>";
            return fileTypesHtmlTable;
        }

        /// <summary>
        /// Create a HTML table of the top level item list.
        /// </summary>
        /// <returns></returns>
        public string createItemList()
        {
            String itemList =
                "<B>The following files/directories were backed up</B><BR>" +
                "<ul>"
                ;
            int max=10;
            foreach (BackupItem item in m_Items)
            {
                if(item.m_enabled) {
                    itemList += "<li>" + item.m_path + "</li>";
                    if (max <=0 )
                    {
                        itemList += "<li>...</li>";
                        break;
                    }
                    --max;
                }
            }
            itemList += "</ul>";
            return itemList;
        }

        /// <summary>
        /// Return the list of recently created directories.
        /// </summary>
        /// <returns></returns>
        public string createRecentlyCreatedDirectoryList()
        {
            string list = "";
            if (m_Statistics.m_dirsCreated.Count > 0)
            {
                int max = 5;
                list = "<BR>The following new directories were backed up<BR>";
                list += "<ul>";
                foreach (string s in m_Statistics.m_dirsCreated)
                {
                    list += "<li>" + s + "</li>";
                    if (max <= 0)
                    {
                        list += "<li>...</li>";
                    }
                    --max;
                }
                list += "</ul>";
            }

            if (m_Statistics.m_dirsPruned.Count > 0)
            {
                int max = 5;
                list += "<BR>The following directories were removed<BR>";
                list += "<ul>";
                foreach (string s in m_Statistics.m_dirsPruned)
                {
                    list += "<li>" + s + "</li>";
                    if (max <= 0)
                    {
                        list += "<li>...</li>";
                    }
                    --max;
                }
                list += "</ul>";
            }
            if (list.Length > 0)
            {
                list += "<BR>";
            }
            return list;
        }
    };
}
